package org.six11.olive3.net;

import java.io.*;
import java.net.BindException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

import org.antlr.runtime.RecognitionException;
import org.six11.olive3.*;
import org.six11.olive3.client.OliveState;
import org.six11.olive3.net.Message.Param;
import org.six11.olive3.net.Message.Statement;
import org.six11.olive3.util.MiscFunctions;
import org.six11.olive3.util.MultiState;
import org.six11.util2.Debug;

import sun.misc.resources.Messages;

/**
 * 
 * 
 * @author Gabe Johnson <johnsogg@cmu.edu>
 * 
 */
public class OliveNetworkTransmitter {

  private URL url;
  private Map<String, List<String>> messages;
  private OliveStreamHandler handler;
  private String lastMessageIndex = "0";
  //private OliveState state;
  private MultiState state;

  public OliveNetworkTransmitter(/*final OliveState state_*/ final MultiState state_, URL url) {
    this.state = state_;
    this.url = url;
    this.messages = new LinkedHashMap<String, List<String>>();
    this.handler = new OliveStreamHandler();
    final Thread downstreamThread = new DownstreamNetworkThread();
    if (state.getValue(MessageNames.PARAM_USER_ID) == null) {
      handler.registerSynchronousHandler(MessageNames.MSG_LOGIN_REPLY, new MessageHandler() {
        public void handleNow(Message m, PrintWriter out) {
          state.setValue(MessageNames.PARAM_USER_ID, m.params.get(0).value);
          downstreamThread.start();
        }
      });
    } else {
      downstreamThread.start(); // in a live context we may already have a user ID.
    }

    handler.registerAsynchronousHandler(MessageNames.MSG_LAST_MSG_IDX, new MessageHandler() {
      public void handleLater(Message m) {
        lastMessageIndex = m.getParamValue(MessageNames.PARAM_MESSAGE_INDEX);
      }
    });
    Thread upstreamThread = new UpstreamNetworkThread();
    upstreamThread.start();
  }

  public OliveStreamHandler getStreamHandler() {
    return handler;
  }

  /**
   * Adds a statement to a message, which will be sent to the server on the next upload connect.
   * 
   * @param signature
   * @param statement
   */
  public void addToMessage(String signature, String statement) {
    synchronized (messages) {
      List<String> statements = findOrCreateStatementList(signature);
      statements.add(statement);
      messages.notifyAll();
    }
  }

  /**
   * Adds a message that contains no statements. Note that the implementation does not transmit more
   * than one message with the same signature. So if you add two bodiless messages with the same
   * signature, only one will be sent at the next transmission upload. The second will be 'lost' (as
   * it was never recorded to begin with).
   */
  public void addBodilessMessage(Message m) {
    // 'messages' is a map of signatures to a list of statements.
    addToMessage(m.getSignature(), "");
  }

  private static void bug(String what) {
    Debug.out("OliveNetworkTransmitter", what);
  }

  /**
   * Gives the statement list for the given message signature. A list is created if it does not
   * currently exist.
   * 
   * @param signature
   * @return
   */
  private List<String> findOrCreateStatementList(String signature) {
    if (messages.containsKey(signature) == false) {
      messages.put(signature, new ArrayList<String>());
    }
    return messages.get(signature);
  }

  void upload(String msg) {
    try {
//       bug("Sending message:\n" + msg);
      // connect to the URL
      HttpURLConnection ht = initConnection();
      OutputStreamWriter outWriter = new OutputStreamWriter(ht.getOutputStream());
      outWriter.write(msg);
      outWriter.flush();
      outWriter.close();

      // It is critical that you access the input stream. If you don't, it seems the connection
      // is never actually sent to the server.
      InputStream in = ht.getInputStream();
      handler.handle(in, new PrintWriter(System.out));
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  void download() {
    // connect to the URL and wait until it gives a response. Parse it, and handle whatever messages
    // it provides.
    try {
      HttpURLConnection ht = initConnection();
      OutputStreamWriter outWriter = new OutputStreamWriter(ht.getOutputStream());
      outWriter.write(MessageNames.MSG_REQUEST_DOWNLOAD + " (" + MessageNames.PARAM_MESSAGE_INDEX
          + "=\"" + lastMessageIndex + "\" " + MessageNames.PARAM_USER_ID + "=\""
          + state.getValue(MessageNames.PARAM_USER_ID) + "\") { }\n");
      outWriter.close();
      handler.handle(ht.getInputStream(), null);
    } catch (IOException ex) {
      ex.printStackTrace();
    } catch (RecognitionException ex) {
      ex.printStackTrace();
    }
  }

  private HttpURLConnection initConnection() throws IOException {
    // connect, set up relevant HTTP parameters, return.
    HttpURLConnection ht = (HttpURLConnection) url.openConnection();
    ht.setRequestMethod("POST");
    ht.setUseCaches(false);
    ht.setDoOutput(true);
    ht.setDoInput(true);
    ht.setRequestProperty("Connection", "Keep-Alive");
    return ht;
  }

  class UpstreamNetworkThread extends Thread {
    public void run() {
      try {
        while (true) {
          StringBuffer buf = new StringBuffer();
          synchronized (messages) {
            while (messages.isEmpty()) {
              messages.wait((long) 5000);
            }
            while (!messages.isEmpty()) {
              for (String sig : messages.keySet()) {
                buf.append(sig + "{\n");
                for (String stmt : messages.get(sig)) {
                  buf.append("  " + stmt + "\n");
                }
                buf.append("}\n\n");
              }
              messages.clear();
            }
          }
          upload(buf.toString());
        }
      } catch (InterruptedException ex) {
        ex.printStackTrace();
      }
    }
  }

  class DownstreamNetworkThread extends Thread {
    public void run() {
      try {
        while (true) {
          download();
        }
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
  }
}
